from abc import ABC, abstractmethod
from time import sleep
from os.path import isfile

from playwright.sync_api import sync_playwright, Page
import pandas as pd
from pandas import DataFrame

from common.config import Public_Data, singleton
from common.obsever import Notice
from project.web_comm import get_token_from_page

from api.api_db import update_url_status, update_url, update_status


def para_to_list(para: str):
    para = str(para)

    if para:
        if para[-1] == ",":
            para = para[0:len(para) - 1]
        if para[0] == "[" and (para[-1] == "]"):
            para_str = para[1:len(para) - 1]
            para_list = para_str.split(",")
            return para_list
        else:
            return [para.strip(), ]
    else:
        return []


def excel_df_to_dict(excel_path, sheet_name):
    try:
        df: DataFrame = pd.read_excel(excel_path, sheet_name=sheet_name)
        df_ddl_tread = df.fillna("")
        row_count, col_count = df_ddl_tread.shape
        step_dict = {}
        col_list = df_ddl_tread.columns.to_list()
        for index in range(row_count):
            step_id = df_ddl_tread.loc[index, "id"]
            data_dict = {}
            for col_name in col_list:
                data_dict[col_name] = df_ddl_tread.loc[index, col_name]
            step_dict[step_id] = data_dict
        return step_dict
    except ValueError as e:
        return e


def tuple_to_str(value: str):
    if type(value) is str:
        return value
    if type(value) is tuple:
        return value[0]
    else:
        return ""


def replace_str_para(js_str: str, data_list):
    """字符串中的‘{index+1}’ 用 data_list中的数据替换"""
    for index, value in enumerate(data_list):
        replace_str = "{" + str(index + 1) + "}"
        if replace_str in js_str:
            js_str = js_str.replace(replace_str, data_list[index].strip())
    return js_str


def check_data_none(data):
    if type(data) is str:
        if data.strip():
            return True
        else:
            return False
    elif type(data) is tuple or type(data) is list:
        if len(data) > 0:
            return True
        else:
            return False
    else:
        return data


def run_attr(method, para_list):
    if method:
        if para_list:
            # if len(para_list) ==1:
            #   para = para_list[0]
            #   return  method(para_list[0])
            return method(*para_list)
        else:
            return method()


def remove_token(url_str: str):
    url_list = url_str.split("&")
    new_url_list = []
    for url_sub in url_list:
        if "stoken" not in url_sub:
            new_url_list.append(url_sub)
    return "&".join(new_url_list)


class Base_Driver(ABC, Notice):
    def __init__(self, data_dict, ddl_path, sheet_name, observer=None):
        super().__init__()
        self.data_dict = data_dict
        self.ddl_path = ddl_path
        self.sheet_name = sheet_name
        self.step_dict = {}
        self.__get_step_dict()
        if self.step_dict:
            self.keys = self.step_dict.keys()
        self.current_id = 1
        if self.keys:
            self.max_id = max(list(self.keys))
        if observer:
            self.attach(observer)

    def __get_step_dict(self):
        if isfile(self.ddl_path):
            if self.sheet_name:
                self.step_dict = excel_df_to_dict(self.ddl_path, self.sheet_name)
                if type(self.step_dict) is not dict:
                    self.notify(self.step_dict)
                    self.step_dict = None
                    return
            else:
                self.notify(f"无处理步骤的表单名")
        else:
            self.notify(f"网页驱动文件{self.ddl_path}不存在或不是文件")

    def start_step(self):
        while self.current_id in self.keys and self.current_id <= self.max_id:
            step_dict = self.step_dict[self.current_id]
            result = self.run_step(step_dict)
            if not result:
                return False
            self.current_id = step_dict["id"] + 1
        self.notify(f"完成了{self.sheet_name}的相关处理")
        return True

    @abstractmethod
    def run_step(self, step_dict):
        pass


class WebDriver(Base_Driver):
    def __init__(self, data_dict, ddl_path, sheet_name, observer=None):
        super().__init__(data_dict, ddl_path, sheet_name, observer)
        self.page = None

    # region 运行方案
    # 1 获取流程数据
    def start_flow(self):
        with sync_playwright() as p:
            browser = p.chromium.launch(headless=False)
            self.page = browser.new_page()
            self.start_step()

    def run_step(self, step_dict):
        model_sheet_name = step_dict["model_name"].strip()
        data_dict_str = step_dict["data_dict"].strip()
        data_dict = {}
        if data_dict_str:
            if data_dict_str.strip() == "main_data":
                data_dict = self.data_dict
            elif data_dict_str in self.data_dict.keys():
                data_dict = self.data_dict[data_dict_str]

        flow_model = Flow_Model(self.ddl_path, model_sheet_name, self.page, data_dict, self.observers)
        result = flow_model.start_step()
        return result

    # endregion


@singleton
class P_Driver:
    """程序中的文件操作"""

    def __init__(self) -> None:
        self.page = None
        self.public_data = Public_Data()
        self.flow_model: Flow_Model = None

    # region 方法

    def open_url_whit_token(self, url):
        """获取token 并把token加入到网址中，并打开"""
        public_data = self.public_data
        if not public_data.Token:
            public_data.Token = get_token_from_page(self.page)

        if not public_data.Token:
            return False

        url = url + f"&{public_data.Token}"
        self.open_url(url)

    def selected_el_by_attr_value(self, attr_name, attr_value):
        els = self.page.locator(f'[{attr_name} ="{attr_value}" ]')
        return els

    def click_el_by_attr_value(self, attr_name, attr_value):
        attr_value = tuple_to_str(attr_value)
        els = self.selected_el_by_attr_value(attr_name, attr_value)
        if els.count():
            els.click()

    def check_load(self, selector_str, timeout=60000):
        """等待并检查元素是否加载成功"""
        try:
            self.page.wait_for_selector(selector_str, timeout=timeout)
        except Exception as e:
            return f"获取网页验证超时：{e}"

    def click_el(self, selector_str):
        """获取并点击元素"""
        alert_ = self.page.locator(selector_str)
        if alert_.count():
            alert_.click()

    def click_by_text(self, txt):
        txt = tuple_to_str(txt)
        self.page.get_by_text(txt).click()

    def select_option_by_id(self, value_list):
        id_str = value_list[0].strip()
        value = value_list[1].strip()
        if id_str[0] != "#":
            id_str = f'#{id_str}'
        self.page.select_option(id_str, value)
        el = self.page.locator(id_str)
        el.select_option()

    def open_url(self, url):
        self.page.goto(url)
        return f"已打开网页{url}"

    def execute_js(self, js_str: str, data_list=None):
        new_js_str = js_str
        if data_list:
            new_js_str = replace_str_para(js_str, data_list)
        js_code = '()=>{return ' + new_js_str + ';}'
        self.page.evaluate(js_code)

    def fill_text(self, selector_str, txt):
        selector_str = tuple_to_str(selector_str)
        txt = tuple_to_str(txt)
        self.page.fill(selector_str, txt)

    def test_el(self, locator_str):
        el = self.page.locator(locator_str)
        num = el.count()
        print(num)

    def check_el_inner_text(self, str_list, data_value):
        text = self.page.frame_locator(str_list[0]).locator(str_list[1]).inner_text()
        while text.strip() != data_value:
            sleep(0.1)
            text = self.page.frame_locator(str_list[0]).locator(str_list[1]).inner_text()

    def click_by_role(self, str_list):
        self.page.get_by_role(str_list[0], name=str_list[1]).click()

    def click_by_label(self, label_txt):
        self.page.get_by_label(label_txt).click()

    def check_els_count(self, str_list):
        els = self.page.locator(str_list[0]).all()
        count = len(els)
        while count > int(str_list[1]):
            els = self.page.locator(str_list[0]).all()
            count = len(els)

    def wait_to_click(self, el_str):
        self.page.wait_for_selector(el_str)
        self.click_el(el_str)

    def lookup_el_click(self, str_list, attr_value):
        """
        str_list  0 :所有行的元素  1：判断是否符合的行， 2：操作的行
        attr_value： 0  需要比对的值
        """
        els = self.page.locator(str_list[0])

    def lookup_el_fill(self, str_list, attr_list):
        """
         str_list  0 :所有行的元素  1：判断是否符合的行， 2：操作的行
         attr_list： 0  需要比对的值  1 需要填充的值
         """
        pass

    def lookup_el_fill_selected(self, str_list, attr_list):
        """
                str_list  0 :所有行的元素  1：判断是否符合的行， 2：操作的行
                attr_list： 0  需要比对的值  1 需要选择的值
                """
        pass

    def check_step(self, step_list, value):
        """根据数据的属性是否有值判断跳转至那个步骤"""
        print(step_list)
        step = 0
        if value and step_list[0]:
            step = step_list[1]

            self.row_index = self.__get_step_index(step_list[1])
        else:
            self.row_index = self.__get_step_index(step_list[2])
            tep = step_list[2]
        print(f"经检查执行第{self.row_index}步")

    def wait_time(self, time_num):
        sleep(float(time_num))

    def operate_frame_el(self, locator_str, operate_name, value=""):
        el = self.current_frame.locator(locator_str)
        if el:
            method = getattr(el, operate_name)
            if value:
                method(*value)
            else:
                method()

    def find_el_click(self, locator_str, time=0.5):
        sleep(float(time))
        alert_ = self.page.locator(locator_str)
        if alert_.count():
            alert_.click()

    def check_data(self, data):
        if check_data_none(data):
            return True
        else:
            self.flow_model.current_id = self.flow_model.max_id + 10

    # region 保存数据
    def save_url_and_status_to_db(self, table_name, data_id):
        data_id = tuple_to_str(data_id)
        url = self.page.url
        if url:
            new_url = remove_token(url)
            update_url_status(table_name.strip(), data_id, new_url, 1)

    def save_url(self, table_name, data_id):
        data_id = tuple_to_str(data_id)
        url = self.page.url
        if url:
            new_url = remove_token(url)
            update_url(table_name.strip(), int(data_id), new_url)

    def save_status(self, table_name, status, data_id):
        data_id = tuple_to_str(data_id)
        update_status(table_name.strip(), int(data_id), int(status))

    # endregion

    # region frame 操作

    def set_current_frame(self, current_frame_str):
        self.current_frame = self.page.frame_locator(current_frame_str)

    def click_frame_el_by_text(self, str_list):
        self.page.frame_locator(str_list[0]).get_by_text(str_list[1]).click()

    def click_frame_el_by_text_new(self, loc_str, txt):
        self.page.frame_locator(loc_str).get_by_text(txt).click()

    def click_frame_el_by_locator(self, str_list):
        self.page.frame_locator(str_list[0]).locator(str_list[1]).click()

    def click_frame_el_by_locator_new(self, str1, str2):
        self.page.frame_locator(str1).locator(str2).click()

    def check_frame_el_inner_text(self, locator_str, data_value):
        text = ""
        i = 30
        data_value = tuple_to_str(data_value)
        while text.strip() != data_value and i:
            sleep(0.2)
            text = self.current_frame.locator(locator_str).inner_text()
            i -= 1
        if i > 0:
            return True
        else:
            return False

    def fill_frame_el(self, str_list, data_value):
        if data_value:
            value = tuple_to_str(data_value)
            self.page.frame_locator(str_list[0]).locator(str_list[1]).fill(value)

    def fill_frame_el_new(self, frame_str, locator_str, data_value):
        value = tuple_to_str(data_value)
        self.page.frame_locator(frame_str).locator(locator_str).fill(value)
        print("ok")

    # endregion


class Flow_Model(Base_Driver):

    # 初始化
    def __init__(self, ddl_path, sheet_name, page: Page, data_dict: dict, observer=None):
        super().__init__(data_dict, ddl_path, sheet_name, observer)
        self.page = page
        self.p_driver = P_Driver()
        self.p_driver.flow_model = self
        if not self.p_driver.page:
            self.p_driver.page = self.page

    # region 运行驱动

    def run_step(self, step_dict):
        method_source = step_dict["method_source"]
        method_name = step_dict["method_name"]
        method_para_dict = step_dict["method_para_dict"]
        operate_name = step_dict["operate_name"]
        operate_dict = step_dict["operate_dict"]
        msg = f'正在执行{step_dict["id"]}-{step_dict["step_name"]}的执行'
        self.notify(msg)
        new_attr = self.__run_method(method_source, method_name, method_para_dict)
        if new_attr and operate_name:
            new_para_list = self.__tread_para_dict_str(operate_dict)
            new_fun = getattr(new_attr, operate_name)
            run_attr(new_fun, new_para_list)

        return True

    def __run_method(self, method_source, method_name, method_para_dict):
        para_list = self.__tread_para_dict_str(method_para_dict)
        method = None
        if method_source == "self":
            method = getattr(self.p_driver, method_name)
        elif method_source == "page":
            method = getattr(self.page, method_name)

        return run_attr(method, para_list)

    def __tread_para_dict_str(self, para_dict_str: str):
        if not para_dict_str:
            return []
        para_list = []
        normal_data = ""
        data_attr_str = ""
        if "data_dict" in str(para_dict_str):
            para_str_list = para_dict_str.split("data_dict:")
            if para_str_list[0].strip():
                normal_data = para_str_list[0].strip()
            data_attr_str = para_str_list[1].strip()

        else:
            normal_data = para_dict_str

        if normal_data:
            para_list = para_to_list(normal_data)
        if data_attr_str:
            data_list: list = self.__get_attr_data_list(data_attr_str)
            if not para_list:
                return tuple(data_list)
            para_list.append(tuple(data_list))
        return tuple(para_list)

    def __get_attr_data_list(self, data_attr_str):
        dict_attr_list = para_to_list(data_attr_str)
        data_dict_attr_datas = []
        for dict_attr in dict_attr_list:
            data_dict_attr_datas.append(self.data_dict[dict_attr.strip()])
        return data_dict_attr_datas
